package base;

import java.util.ArrayList;
import java.util.Random;

import configs.Configuracao;
import funcoes.IFuncao;

public class PSO {
	public Particula[] particulas;
	public final double C1=2.05, C2 = 2.05;
	public final double X = 0.72984;
	public final double VELOCIDADE_MAXIMA = 10;
	public final double VELOCIDADE_MINIMA = -10;
	public double U = 1.0;
	public final int MAX = 1;
	public final int MIN = 0;
	public double fitnessGbest;
	public double[] posicaoGbest;
	public ArrayList<Double> historicoConvergencia;
	public int iteracao = 0;

	public PSO(IFuncao funcao)
	{
		this.historicoConvergencia = new ArrayList<Double>();
		fitnessGbest = Double.MAX_VALUE;
		particulas = new Particula[Configuracao.NUMERO_PARTICULAS];
		for(int i = 0; i< Configuracao.NUMERO_PARTICULAS; i++)
		{
			particulas[i] = new Particula(funcao);
		}
		
		for(int i = 0; i< Configuracao.NUMERO_PARTICULAS; i++)
		{
			if(particulas[i].fitnessPbest < fitnessGbest)
			{
				fitnessGbest = particulas[i].fitnessPbest;
				posicaoGbest = particulas[i].posicaoPbest;
			}
		}
	}

	//movimento GLOBAL
	public void movimentoGlobal()
	{
		Random rand = new Random();
		for(int i=0; i<Configuracao.NUMERO_PARTICULAS; i++)
		{
			//atualizando velocidade 
			for(int j=0; j<Configuracao.DIMENSOES; j++)
			{
				particulas[i].velocidade[j] =  U*particulas[i].velocidade[j] +  C1*rand.nextDouble()*(particulas[i].posicaoPbest[j]- particulas[i].posicao[j]) + C2*rand.nextDouble()*(posicaoGbest[j] - particulas[i].posicao[j]);
				//velocidade com fator de constriccao(X)
//				particulas[i].velocidade[j] = X*( particulas[i].velocidade[j] +  C1*rand.nextDouble()*(particulas[i].posicaoPbest[j]- particulas[i].posicao[j]) + C2*rand.nextDouble()*(posicaoGbest[j] - particulas[i].posicao[j]));
				if(particulas[i].velocidade[j] > VELOCIDADE_MAXIMA)
				{
					particulas[i].velocidade[j] = VELOCIDADE_MAXIMA;
				}
				else if(particulas[i].velocidade[j] < VELOCIDADE_MINIMA)
				{
					particulas[i].velocidade[j] = VELOCIDADE_MINIMA;
				}
			}
			//atualizando posicao
			for(int j=0; j<Configuracao.DIMENSOES; j++)
			{
				particulas[i].posicao[j] += particulas[i].velocidade[j];
				if(particulas[i].posicao[j] > Configuracao.ESPACO_BUSCA[1])
				{
					//fica no limite do espaco de busca
					particulas[i].posicao[j] = Configuracao.ESPACO_BUSCA[1];
					//inverte a velocidade
					particulas[i].velocidade[j] = -particulas[i].velocidade[j];
				}
				else if(particulas[i].posicao[j] < Configuracao.ESPACO_BUSCA[0])
				{
					//fica no limite do espaco de busca
					particulas[i].posicao[j] = Configuracao.ESPACO_BUSCA[0];
					//inverte a velocidade
					particulas[i].velocidade[j] = -particulas[i].velocidade[j];
				}
			}
		}
	}
	//movimento LOCAL
	
	public double[] getPosicaoBest(int indice)
	{
		int melhorLocal=indice;
		int indiceMaior, indiceMenor;
		double melhorFitness = particulas[indice].fitnessPbest;
		indiceMaior = (indice+1)%Configuracao.NUMERO_PARTICULAS;
		indiceMenor = (indice + Configuracao.NUMERO_PARTICULAS -1)%Configuracao.NUMERO_PARTICULAS;
		if(particulas[indiceMenor].fitnessPbest < melhorFitness )
		{
			melhorLocal=indiceMenor;
			melhorFitness = particulas[indiceMenor].fitnessPbest;
		}
		if(particulas[indiceMaior].fitnessPbest < melhorFitness)
		{
			melhorLocal = indiceMaior;
		}

		return particulas[melhorLocal].posicaoPbest;
	}
		
	public void movimentoLocal()
	{
		Random rand = new Random();
		for(int i=0; i<Configuracao.NUMERO_PARTICULAS; i++)
		{
			double[] bestPosicaoLocal = getPosicaoBest(i);
			//atualizando velocidade 
			for(int j=0; j<Configuracao.DIMENSOES; j++)
			{	
				particulas[i].velocidade[j] =  U*particulas[i].velocidade[j] +  C1*rand.nextDouble()*(particulas[i].posicaoPbest[j]- particulas[i].posicao[j]) + C2*rand.nextDouble()*(bestPosicaoLocal[j] - particulas[i].posicao[j]);
				//velocidade com fator de constriccao(X)
//				particulas[i].velocidade[j] = X*( particulas[i].velocidade[j] +  C1*rand.nextDouble()*(particulas[i].posicaoPbest[j]- particulas[i].posicao[j]) + C2*rand.nextDouble()*(posicaoGbest[j] - particulas[i].posicao[j]));
				if(particulas[i].velocidade[j] > VELOCIDADE_MAXIMA)
				{
					particulas[i].velocidade[j] = VELOCIDADE_MAXIMA;
				}
				else if(particulas[i].velocidade[j] < VELOCIDADE_MINIMA)
				{
					particulas[i].velocidade[j] = VELOCIDADE_MINIMA;
				}
			}
			//atualizando posicao
			for(int j=0; j<Configuracao.DIMENSOES; j++)
			{
				particulas[i].posicao[j] += particulas[i].velocidade[j];
				if(particulas[i].posicao[j] > Configuracao.ESPACO_BUSCA[1])
				{
					//fica no limite do espaco de busca
					particulas[i].posicao[j] = Configuracao.ESPACO_BUSCA[1];
					//inverte a velocidade
					particulas[i].velocidade[j] = -particulas[i].velocidade[j];
				}
				else if(particulas[i].posicao[j] < Configuracao.ESPACO_BUSCA[0])
				{
					//fica no limite do espaco de busca
					particulas[i].posicao[j] = Configuracao.ESPACO_BUSCA[0];
					//inverte a velocidade
					particulas[i].velocidade[j] = -particulas[i].velocidade[j];
				}
			}
		}
	}
		
	public void avaliar()
	{
		for(int i=0; i<Configuracao.NUMERO_PARTICULAS; i++)
		{
			particulas[i].avaliar();
			if(particulas[i].fitnessPbest < fitnessGbest)
			{
				fitnessGbest = particulas[i].fitnessPbest;
				posicaoGbest = particulas[i].posicaoPbest.clone();
			}
		}
	}
	
	public void run()
	{
		for(int i = 0; i < Configuracao.ITERACOES; i++)
		{
//			if(i%1000 == 0 && U > 0.45)
//			{
//				U-= 0.1;
//			}
			U = currentW();
			//calculando movimentacao
			movimentoGlobal();
//			movimentoLocal();
			//avaliando
			avaliar();
//			if(i%50 == 0)
//			{
//				System.out.println("Fitness gbest na iteracao "+i+": "+fitnessGbest);		
//			}
			
			historicoConvergencia.add(fitnessGbest);
			
			iteracao++;
		}
	}
	
	private double currentW()
	{
		double vInicial = Configuracao.W[0];
		double vFinal = Configuracao.W[1];
		
		double resp = vInicial + ((vFinal-vInicial)/Configuracao.ITERACOES)*iteracao;
		
		return resp;
	}
}
